import { join, relative, resolve } from 'path';
import { normalizeImportExtension, Types } from '@graphql-codegen/plugin-helpers';
import { BaseVisitor, getConfigValue } from '@graphql-codegen/visitor-plugin-common';
import { concatAST, isScalarType } from 'graphql';
import { buildModule } from './builder.js';
import { ModulesConfig } from './config.js';
import { groupSourcesByModule, isGraphQLPrimitive, normalize, stripFilename } from './utils.js';

export const preset: Types.OutputPreset<ModulesConfig> = {
  buildGeneratesSection: options => {
    const { baseOutputDir } = options;
    const { baseTypesPath, encapsulateModuleTypes } = options.presetConfig;
    const useGraphQLModules = getConfigValue(options?.presetConfig.useGraphQLModules, true);
    const requireRootResolvers = getConfigValue(options?.presetConfig.requireRootResolvers, false);
    const useTypeImports = getConfigValue(options?.config.useTypeImports, false) || false;

    const cwd = resolve(options.presetConfig.cwd || process.cwd());
    const importTypesNamespace = options.presetConfig.importTypesNamespace || 'Types';

    if (!baseTypesPath) {
      throw new Error(
        `Preset "graphql-modules" requires you to specify "baseTypesPath" configuration and point it to your base types file (generated by "typescript" plugin)!`
      );
    }

    if (!options.schemaAst?.extensions.sources) {
      throw new Error(`Preset "graphql-modules" requires to use GraphQL SDL`);
    }

    const extensions: any = options.schemaAst!.extensions;
    const sourcesByModuleMap = groupSourcesByModule(extensions.extendedSources, baseOutputDir);
    const modules = Object.keys(sourcesByModuleMap);

    const baseVisitor = new BaseVisitor(options.config, {});

    // One file with an output from all plugins
    const baseOutput: Types.GenerateOptions = {
      filename: resolve(cwd, baseOutputDir, baseTypesPath),
      schema: options.schema,
      documents: options.documents,
      plugins: [
        ...options.plugins,
        {
          'modules-exported-scalars': {},
        },
      ],
      pluginMap: {
        ...options.pluginMap,
        'modules-exported-scalars': {
          plugin: schema => {
            const typeMap = schema.getTypeMap();

            return Object.keys(typeMap)
              .map(t => {
                if (t && typeMap[t] && isScalarType(typeMap[t]) && !isGraphQLPrimitive(t)) {
                  const convertedName = baseVisitor.convertName(t);
                  return `export type ${convertedName} = Scalars["${t}"];`;
                }

                return null;
              })
              .filter(Boolean)
              .join('\n');
          },
        },
      },
      config: {
        ...options.config,
        enumsAsTypes: true,
      },
      schemaAst: options.schemaAst!,
      documentTransforms: options.documentTransforms,
    };

    const importExtension = normalizeImportExtension({
      emitLegacyCommonJSImports: options.config.emitLegacyCommonJSImports,
      importExtension: options.config.importExtension,
    });

    const baseTypesFilename = baseTypesPath.replace(
      /\.(js|ts|d.ts)$/,
      // we need extension if ESM modules are used
      importExtension
    );
    const baseTypesDir = stripFilename(baseOutput.filename);

    // One file per each module
    const outputs: Types.GenerateOptions[] = modules.map(moduleName => {
      const filename = resolve(cwd, baseOutputDir, moduleName, options.presetConfig.filename);
      const dirpath = stripFilename(filename);
      const relativePath = relative(dirpath, baseTypesDir);
      const importPath = options.presetConfig.importBaseTypesFrom || normalize(join(relativePath, baseTypesFilename));
      const sources = sourcesByModuleMap[moduleName];

      const moduleDocument = concatAST(sources.map(source => source.document));

      const shouldDeclare = filename.endsWith('.d.ts');

      return {
        filename,
        schema: options.schema,
        documents: [],
        plugins: [
          ...options.plugins.filter(p => typeof p === 'object' && !!p.add),
          {
            'graphql-modules-plugin': {},
          },
        ],
        pluginMap: {
          ...options.pluginMap,
          'graphql-modules-plugin': {
            plugin: schema =>
              buildModule(moduleName, moduleDocument, {
                importNamespace: importTypesNamespace,
                importPath,
                encapsulate: encapsulateModuleTypes || 'namespace',
                requireRootResolvers,
                shouldDeclare,
                schema,
                baseVisitor,
                useGraphQLModules,
                rootTypes: [
                  schema.getQueryType()?.name,
                  schema.getMutationType()?.name,
                  schema.getSubscriptionType()?.name,
                ].filter(Boolean),
                useTypeImports,
              }),
          },
        },
        config: options.config,
        schemaAst: options.schemaAst,
        documentTransforms: options.documentTransforms,
      };
    });

    return [baseOutput].concat(outputs);
  },
};

export default preset;
